
/* GCSx
** DEBUG.CPP
**
** Debugging functions, including console I/O
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

// Debug level
int currentDebugLevel = 0;

int debugLevel(int newLevel) {
    if (newLevel >= 0) currentDebugLevel = newLevel;
    return currentDebugLevel;
}

// Debugging console

DebugWin* debug = NULL;
list<string>* debugText = NULL;
int inExitStage = 0;
char* dwBuffer = NULL;

int DebugWin::wantsToBeDeleted() const { start_func
    return 0;
}

DebugWin::DebugWin() : EditBox(debugText, FONT_MONO, 1), commandHistory() { start_func
    commandHistoryPosition = 0;
    newCommand = 1;
}

DebugWin::~DebugWin() { start_func
}

void DebugWin::selectAllConsole() { start_func
    cursorToStart(0);
    cursorToEnd(1);
}

void DebugWin::cursorToStart(int drag) { start_func
    TextPoint point;
    findDLine(dNumLines - 1, &point.vRow);
    point.column = CONSOLE_LENGTH;
    insertionState(point, drag);
}

void DebugWin::cursorToEnd(int drag) { start_func
    TextPoint point;
    point.vRow = vNumLines - 1;
    point.column = (*findVLine(vNumLines - 1)).length;
    insertionState(point, drag);
}

// Typing-related events handled like a console
int DebugWin::event(int hasFocus, const SDL_Event* event) { start_func
    string clipStorage;
    TextPoint point;
    list<string>::iterator pointLine;

    int onLastLine = 0;
    int onLastLineInitial = 0;
    VLine* vl = &*findVLine(insertion.vRow);
    if ((vl->dLine == dNumLines - 1) && (vl->colStart + insertion.column >= CONSOLE_LENGTH)) {
        if (vl->colStart + insertion.column == CONSOLE_LENGTH) onLastLineInitial = 1;
        onLastLine = 1;
    }
    if (isSelect) {
        vl = &*findVLine(selectBegin.vRow);
        if ((vl->dLine < dNumLines - 1) || (vl->colStart + selectBegin.column < CONSOLE_LENGTH)) onLastLine = 0;
    }
    
    // Readonly unless solely on last line
    if (onLastLine) readonly = 0;

    switch (event->type) {
        case SDL_COMMAND:
            switch (event->user.code) {
                case EDIT_PASTE:
                    // Only allow pasting single line text
                    if (!canConvertClipboard(CLIPBOARD_TEXT_LINE)) readonly = 1;
                    break;

                case EDIT_SELECTALL:
                    selectAllConsole();
                    return 1;
            }
            break;

        case SDL_KEYDOWN: {
            int drag = 0;
            Sint32 key;

            if (event->key.keysym.mod & KMOD_SHIFT) {
                drag = 1; // We'll check this if we care about SHIFT
                key = combineKey(event->key.keysym.sym, event->key.keysym.mod & ~KMOD_SHIFT);
            }
            else {
                key = combineKey(event->key.keysym.sym, event->key.keysym.mod);
            }
        
            switch (key) {
                case SDLK_BACKSPACE:
                    // Disallow if first character of console line and no selection
                    if ((onLastLineInitial) && (!isSelect)) readonly = 1;
                    break;
                    
                case SDLK_DOWN:
                case SDLK_UP:
                    // Manage command history
                    if (onLastLine) {
                        // Get cmd
                        point = insertion;
                        vl = &*findVLine(point.vRow);
                        string cmd = (*vl->dLineI).substr(CONSOLE_LENGTH, string::npos);

                        // Only spaces?
                        string::size_type pos = 0;
                        pos = cmd.find_first_not_of(' ', pos);
                        // (only spaces present?)
                        if (pos != string::npos) {
                            // Add to command history
                            if ((newCommand) || (commandHistoryPosition >= commandHistory.size())) {
                                if (commandHistoryPosition >= commandHistory.size()) {
                                    // So adding cmd to end of history will allow us to "down" to a blank line
                                    newCommand = 0;
                                    commandHistoryPosition = commandHistory.size();
                                }
                                commandHistory.push_back(cmd);
                                if (commandHistory.size() > MAX_COMMAND_HISTORY) {
                                    commandHistory.erase(commandHistory.begin());
                                    --commandHistoryPosition;
                                }
                            }
                            // Update existing command
                            else if (cmd != commandHistory[commandHistoryPosition]) {
                                commandHistory[commandHistoryPosition] = cmd;
                            }
                        }
                        else if (commandHistoryPosition > commandHistory.size()) {
                            commandHistoryPosition = commandHistory.size();
                        }
                        
                        if (key == SDLK_DOWN) {
                            // If was a new command, we don't move as we didn't
                            // insert that command right where we are
                            if (!newCommand) {
                                if (++commandHistoryPosition > commandHistory.size()) {
                                    commandHistoryPosition = commandHistory.size();
                                }
                            }
                        }
                        else {
                            if (commandHistoryPosition > 0) {
                                --commandHistoryPosition;
                            }
                        }
                        
                        // Show new command
                        selectAllConsole();
                        if (commandHistoryPosition >= commandHistory.size()) {
                            pasteText(blankString);
                            newCommand = 1;
                        }
                        else {
                            pasteText(commandHistory[commandHistoryPosition]);
                            newCommand = 0;
                        }
                        
                        return 1;
                    }
                    break;

                case SDLK_LEFT:
                    // Disallow if first character of console line
                    if (onLastLineInitial) return 1;
                    break;

                case SDLK_HOME:
                case combineKey(SDLK_HOME, KMOD_CTRL):
                    if (onLastLine) {
                        cursorToStart(drag);
                        return 1;
                    }
                    break;

                case SDLK_PAGEUP:
                    if (myFrame) {
                        myFrame->scrollBy(0, max(1, (viewHeight / lineHeight) - 1) * lineHeight);
                    }
                    if (myScroll) {
                        myScroll->scrollBy(0, max(1, (viewHeight / lineHeight) - 1) * lineHeight);
                    }
                    return 1;

                case SDLK_PAGEDOWN:
                    if (myFrame) {
                        myFrame->scrollBy(0, max(1, (viewHeight / lineHeight) - 1) * -lineHeight);
                    }
                    if (myScroll) {
                        myScroll->scrollBy(0, max(1, (viewHeight / lineHeight) - 1) * -lineHeight);
                    }
                    return 1;

                case combineKey(SDLK_LEFT, KMOD_CTRL):
                    // Disallow if first character of console line
                    if (onLastLineInitial) return 1;
                    break;

                case SDLK_TAB:
                    // @TODO: command completion
                    return 1;
                    
                case SDLK_RETURN:
                case SDLK_KP_ENTER:
                    if (onLastLine) {
                        // Get cmd
                        point = insertion;
                        vl = &*findVLine(point.vRow);
                        string cmd = (*vl->dLineI).substr(CONSOLE_LENGTH, string::npos);

                        // Only spaces?
                        string::size_type pos = 0;
                        pos = cmd.find_first_not_of(' ', pos);
                        // (only spaces present?)
                        if (pos != string::npos) {
                            // Add to command history
                            if ((newCommand) || (commandHistoryPosition >= commandHistory.size())) {
                                commandHistory.push_back(cmd);
                                if (commandHistory.size() > MAX_COMMAND_HISTORY) {
                                    commandHistory.erase(commandHistory.begin());
                                }
                                commandHistoryPosition = commandHistory.size();
                            }
                            // Update existing history
                            else {
                                if (cmd != commandHistory[commandHistoryPosition]) {
                                    commandHistory[commandHistoryPosition] = cmd;
                                }
                                // After this, "up" should put us at the same command
                                ++commandHistoryPosition;
                            }
                        }
                        
                        // Add new prompt
                        cursorToEnd();
                        pasteText("\n");
                        pasteText(CONSOLE_PROMPT);
                        newCommand = 1;

                        // Process cmd
                        consoleParse(cmd);
                        return 1;
                    }
                    break;
                    
                default:
                    // Special-case to ignore default VIEW_CONSOLE shortcut key
                    if (key == config->readShortcut(VIEW_CONSOLE)) return 0;
                    break;
            }
            break;
        }
    }
    
    int result = EditBox::event(hasFocus, event);
    readonly = 1;
    return result;
}

void DebugWin::updateTitlebar() { start_func
    if (myFrame) {
        myFrame->setTitle("Console");
    }
}

void DebugWin::prepOpen() { start_func
    EditBox::prepOpen();

    // Move cursor to end
    cursorToEnd();
}

void DebugWin::paintText(const string& text, const string& entireLine, int posInLine, int x, int y, SDL_Surface* destSurface, int isSelected) const { start_func
    if (strcmp(CONSOLE_PROMPT, entireLine.substr(0, CONSOLE_LENGTH).c_str())) {
        drawText(text, guiRGB[isSelected ? COLOR_TEXTBOX : COLOR_TEXT], x, y, destSurface, font);
    }
    else {
        if (posInLine < CONSOLE_LENGTH) {
            drawText(text.substr(0, CONSOLE_LENGTH - posInLine), guiRGB[isSelected ? COLOR_TEXTBOX : COLOR_CONSOLEPROMPT], x, y, destSurface, font);
            if (text.size() > (string::size_type)(CONSOLE_LENGTH - posInLine)) {
                x += fontWidth(text.substr(0, CONSOLE_LENGTH - posInLine), font);
                drawText(text.substr(CONSOLE_LENGTH - posInLine, string::npos), guiRGB[isSelected ? COLOR_TEXTBOX : COLOR_CONSOLEINPUT], x, y, destSurface, font);
            }
        }
        else {
            drawText(text, guiRGB[isSelected ? COLOR_TEXTBOX : COLOR_CONSOLEINPUT], x, y, destSurface, font);
        }
    }
}

void DebugWin::addLine(const char* text) { start_func
    VLine* vl = &*findDLine(dNumLines - 1);
    debugText->insert(vl->dLineI, text);
    contentInsert(dNumLines - 1, 1);
}

void initDebugBuffer() { start_func
    if (!debugText) {
        debugText = new list<string>;
        // Console prompt
        debugText->push_back(CONSOLE_PROMPT);
    }
    if (!dwBuffer) dwBuffer = new char[DEBUG_BUFFER_SIZE];
}

void initDebugWindow() { start_func
    if (!debug) {
        debug = new DebugWin();
        debug->init();
    }
}

void beginExitStage() { start_func
    inExitStage = 1;
}

void exitDebug() { start_func
    delete debug;
    delete[] dwBuffer;
    delete debugText;
    debug = NULL;
    dwBuffer = NULL;
    debugText = NULL;
}

DebugWin* debugWindow() { start_func
    return debug;
}

int inDebug = 0;

void debugClear() { start_func
    inDebug = 1;
    
    if (debug) {
        debug->selectAll();
        debug->pasteText(CONSOLE_PROMPT);
    }
    else if (debugText) {
        debugText->clear();
        debugText->push_back(CONSOLE_PROMPT);
    }
    
    inDebug = 0;
}

void debugStdout(const char* text, va_list& args) {
    if (stdout) {
        vfprintf(stdout, text, args);
        fputc('\n', stdout);
        fflush(stdout);
    }
}

void debugWrite(const char* text, va_list& args) {
    assert(text);
    
    if (inDebug) return;
    inDebug = 1;
    
    if (inExitStage) {
        debugStdout(text, args);
    }
    else {
        if (!dwBuffer) dwBuffer = new char[DEBUG_BUFFER_SIZE];

        vsnprintf(dwBuffer, DEBUG_BUFFER_SIZE, text, args);
        dwBuffer[DEBUG_BUFFER_SIZE - 1] = 0;

        if (stdout) {
            fwrite(dwBuffer, 1, strlen(dwBuffer), stdout);
            fputc('\n', stdout);
            fflush(stdout);
        }

        if (debug) {
            debug->addLine(dwBuffer);
        }
        else if (debugText) {
            // Insert before console prompt
            list<string>::iterator pos = debugText->end();
            --pos;
            debugText->insert(pos, dwBuffer);
        }
    }

    inDebug = 0;
}

void debugStdout(const char* text, ...) {
    va_list arglist;
    va_start(arglist, text);

    debugStdout(text, arglist);

    va_end(arglist);
}

void debugWrite(const char* text, ...) {
    if (inDebug) return;
    
    va_list arglist;
    va_start(arglist, text);

    debugWrite(text, arglist);

    va_end(arglist);
}

void debugStdout(int level, const char* text, ...) {
    if ((level) && (!(currentDebugLevel & level))) return;

    va_list arglist;
    va_start(arglist, text);

    debugStdout(text, arglist);

    va_end(arglist);
}

void debugWrite(int level, const char* text, ...) {
    if (inDebug) return;
    if ((level) && (!(currentDebugLevel & level))) return;

    va_list arglist;
    va_start(arglist, text);

    debugWrite(text, arglist);

    va_end(arglist);
}

void debugDump(const void* data, int size, int toStdOut) {
    if (inDebug) return;
    
    if (!data) {
        if (toStdOut) debugStdout("<NULL>");
        else debugWrite("<NULL>");
        return;
    }
    
    string output;
    const Uint8* data8 = (Uint8*)data;
    
    while (size) {
        output = "";
        for (int pos = 0; (pos < 48) && (size); ++pos, --size, ++data8) {
            if (((pos % 4) == 0) && (pos)) output += " ";
            
            int nib = (*data8) >> 4;
            if (nib < 10) output += '0' + nib;
            else output += 'A' - 10 + nib;

            nib = (*data8) & 0xF;
            if (nib < 10) output += '0' + nib;
            else output += 'A' - 10 + nib;
        }
        
        if (toStdOut) debugStdout(output.c_str());
        else debugWrite(output.c_str());
    }
}

// Other stuff

const string errorTitleInvalidEntry("Invalid Entry");
const string errorTitleVideo("Video Error");
const string errorTitleException("Internal Exception");
const string errorTitleImageOverflow("Image Memory Overflow");
const string errorTitleResourceLoad("Resource Loading Error");
const string errorTitleFile("File Error");
const string errorTitleImport("Import Error");
const string errorTitleDuplicateName("Duplicate Name");
const string errorTitleMissingName("Missing Name");
const string errorTitleInUse("Resource In Use");
const string warnTitleReminder("Reminder");

const string messageBoxYes("\tYes");
const string messageBoxNo("\tNo");
const string messageBoxOK("OK");
const string messageBoxCancel("Cancel");
const string messageBoxReminder("\tDon't show this warning again");

#if defined(WIN32)
#include <windows.h>

void systemErrorBox(const char* text, const char* title) {
    assert(text);

    debugWrite(DEBUG_FATALERROR, text);
    if (title == NULL) {
        MessageBoxA(NULL, text, "An Error Has Occurred", 16);
    }
    else {
        MessageBoxA(NULL, text, title, 16);
    }
}

#else

void systemErrorBox(const char* text, const char* title) {
    debugWrite(DEBUG_FATALERROR, text);
}

#endif

int guiRemindBox(const std::string& text, const std::string& title) { start_func
    Dialog* d = NULL;
    Widget* w = NULL;

    d = new Dialog(title);

    w = new WStatic(0, text);
    w->addTo(d);

    int checked = 0;
    w = new WCheckBox(0, messageBoxReminder, &checked, 1);
    w->addTo(d);

    w = new WButton(0, messageBoxOK, Dialog::BUTTON_OK);
    w->addTo(d);

    d->makePretty(1);
    d->runModal();

    delete d;
    return checked;
}

void guiErrorBox(const string& text, const string& title) { start_func
    Dialog* d = NULL;
    Widget* w = NULL;
    
    d = new Dialog(title);

    w = new WStatic(0, text);
    w->addTo(d);

    w = new WButton(0, messageBoxOK, Dialog::BUTTON_OK);
    w->addTo(d);
    
    d->makePretty();
    d->runModal();

    delete d;
}

int guiConfirmBox(const string& text, const string& title) { start_func
    Dialog* d = NULL;
    Widget* w = NULL;
    int result = 0;
    
    d = new Dialog(title);

    w = new WStatic(0, text);
    w->addTo(d);

    w = new WButton(1, messageBoxOK, Dialog::BUTTON_OK);
    w->addTo(d);
    
    w = new WButton(0, messageBoxCancel, Dialog::BUTTON_CANCEL);
    w->addTo(d);
    
    d->makePretty();
    result = d->runModal();

    delete d;
    return result;
}

int guiMessageBox(const string& text, const string& title, const string* buttonA, const string* buttonB, const string* buttonC, const string* buttonD, const string* buttonE) { start_func
    Dialog* d = NULL;
    Widget* w = NULL;
    int result = 0;
    
    d = new Dialog(title);

    w = new WStatic(0, text);
    w->addTo(d);

    if (buttonA) {
        w = new WButton(1, *buttonA, Dialog::BUTTON_OK);
        w->addTo(d);
    }
    
    if (buttonB) {
        w = new WButton(2, *buttonB, Dialog::BUTTON_OK);
        w->addTo(d);
    }
    
    if (buttonC) {
        w = new WButton(3, *buttonC, Dialog::BUTTON_OK);
        w->addTo(d);
    }
    
    if (buttonD) {
        w = new WButton(4, *buttonD, Dialog::BUTTON_OK);
        w->addTo(d);
    }
    
    if (buttonE) {
        w = new WButton(5, *buttonE, Dialog::BUTTON_OK);
        w->addTo(d);
    }
    
    d->makePretty();
    result = d->runModal();

    delete d;
    return result;
}

